package org.dslforge.aurum;

import static java.util.Arrays.asList;
import static org.dslforge.aurum.SemanticAction.Type.RecognizeTerminal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class GrammarDefinition {
	public static enum Lexer {
		MAIN {
			Map<SemanticAction, Pattern> getSpecification(GrammarDefinition definition) {
				return definition.specification;
			}
		},
		ESCAPE {
			Map<SemanticAction, Pattern> getSpecification(GrammarDefinition definition) {
				return definition.escapeSpecification;
			}
		};

		abstract Map<SemanticAction, Pattern> getSpecification(GrammarDefinition definition);
	}

	Map<String, Pattern> namedPatterns = new HashMap<String, Pattern>();

	Map<SemanticAction, Pattern> specification = new HashMap<SemanticAction, Pattern>();

	Map<SemanticAction, Pattern> escapeSpecification = new HashMap<SemanticAction, Pattern>();

	Map<Symbol, List<Production>> productions = new HashMap<Symbol, List<Production>>();

	public void definePattern(String name, Pattern pattern) {
		namedPatterns.put(name, pattern);
	}

	public Pattern getPattern(String name) {
		Pattern pattern = namedPatterns.get(name);
		return (Pattern) (pattern == null ? null : pattern.clone());
	}

	public void defineTerminal(Lexer target, String name, Pattern pattern, String... states) {
		target.getSpecification(this).put(
				new SemanticAction(RecognizeTerminal, name, asList(states)), pattern);
	}

	public boolean isTerminalDefined(Lexer target, String name) {
		return target.getSpecification(this).containsKey(
				new SemanticAction(RecognizeTerminal, name, asList("$initial")));
	}

	Automata<CharSet, Collection<SemanticAction>> computeLexicalDFA(
			Map<SemanticAction, Pattern> specification) {
		Automata<CharSet, SemanticAction> NFA = new Automata<CharSet, SemanticAction>(1);
		for (Entry<SemanticAction, Pattern> declaration : specification.entrySet()) {
			Pattern pattern = declaration.getValue();
			int start = NFA.mergeTransitions(pattern.automata);
			NFA.connect(0, CharSet.EPSILON, start);
			NFA.setAccept(start + pattern.tail, declaration.getKey());
		}
		return new HopcroftMinimizer(new SubsetDeterminizer(NFA).determinize()).minimize();
	}

	public void addProduction(Symbol nonterminal, Symbol... symbols) {
		if (!nonterminal.isNonterminal)
			throw new IllegalArgumentException();
		List<Production> productionList = productions.get(nonterminal);
		if (productionList == null) {
			productionList = new ArrayList<Production>();
			productions.put(nonterminal, productionList);
		}
		Production production = new Production(nonterminal, symbols);
		if (!productionList.contains(production))
			productionList.add(production);
	}
}

class SemanticAction {
	static enum Type {
		RecognizeTerminal, BeginState, EndState, UserDefinied;
	}

	Type type;

	String identifier;

	Collection<String> states = new HashSet<String>();

	SemanticAction(Type type, String identifier, Collection<String> states) {
		this.type = type;
		this.identifier = identifier;
		this.states.addAll(states);
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null || obj.getClass() != getClass())
			return false;
		SemanticAction action = (SemanticAction) obj;
		return action.type == type && action.identifier.equals(identifier);
	}

	@Override
	public int hashCode() {
		return type.hashCode() * 37 + identifier.hashCode();
	}
}